15-2 数据库代码初步优化:实现抽象层repository
一、数据库模块重构
1.1 创建核心数据库模块
// 创建database模块
nest g mo database
typescript
背景知识
在NestJS中,模块是应用程序的基本组织单元,每个模块封装了一组相关的功能。通过将数据库相关代码集中到database
模块中,可以实现以下目标:
- 解耦:将数据库逻辑与业务逻辑分离
- 复用:多个功能模块可以共享数据库连接和操作
- 维护性:集中管理数据库配置和迁移
实践案例
假设我们有一个电商系统,需要管理用户、商品和订单数据。通过创建database
模块:
- 统一管理所有实体定义
- 集中配置数据库连接
- 提供跨模块的数据访问服务
常见问题
❓ 为什么需要单独创建database模块? ✅ 避免数据库配置分散在各个业务模块中,方便统一管理和维护
💡 提示:可以使用环境变量动态配置数据库连接参数,实现开发/生产环境无缝切换
1.2 拆分数据库实现
1.2.1 TypeORM模块封装
// 创建typeorm通用模块
nest g mo database/typeorm/typeorm-common
typescript
技术细节
- 实体管理:在模块中定义所有TypeORM实体
- 连接池配置:优化数据库连接性能
- 事务管理:提供统一的事务处理机制
示例配置
// typeorm-common.module.ts
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
entities: [__dirname + '/../**/*.entity{.ts,.js}']
}),
TypeOrmModule.forFeature([UserEntity, ProductEntity])
]
})
export class TypeormCommonModule {}
typescript
1.2.2 Prisma模块封装
// 创建prisma通用模块
nest g mo database/prisma/prisma-common
typescript
前沿技术
Prisma作为新一代ORM工具,提供:
- 类型安全:自动生成的TypeScript类型定义
- 直观查询:链式API设计
- 数据迁移:内置迁移工具
最佳实践
// prisma-common.module.ts
@Module({
providers: [PrismaService],
exports: [PrismaService]
})
export class PrismaCommonModule {}
typescript
1.2.3 Mongoose模块封装
// 创建mongoose通用模块
nest g mo database/mongoose/mongoose-common
typescript
性能优化
- 连接池:配置合理的连接池大小
- 索引优化:确保查询性能
- 模式设计:合理设计文档结构
配置示例
// mongoose-common.module.ts
@Module({
imports: [
MongooseModule.forRoot(process.env.MONGO_URI),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])
]
})
export class MongooseCommonModule {}
typescript
1.3 重构后模块结构
架构优势
- 灵活切换:可根据需求启用/禁用特定数据库模块
- 职责清晰:每个模块只关注特定数据库实现
- 易于测试:可以单独测试每个数据库模块
扩展学习
💡 提示:可以使用装饰器组合进一步简化模块配置,如@DatabaseModule()
自动加载所有子模块
二、抽象层实现
2.1 创建抽象Repository
// user-abstract.repository.ts
export abstract class UserAbstractRepository {
abstract find(): Promise<User[]>;
abstract create(user: User): Promise<User>;
abstract update(id: string, user: Partial<User>): Promise<User>;
abstract delete(id: string): Promise<void>;
}
typescript
设计模式解析
- 抽象工厂模式:定义统一接口,隐藏具体实现
- 依赖倒置原则:高层模块不依赖低层模块,都依赖抽象
- 开闭原则:对扩展开放,对修改关闭
扩展方法建议
// 可扩展的抽象方法
abstract findByEmail(email: string): Promise<User | null>;
abstract search(keyword: string): Promise<User[]>;
abstract count(): Promise<number>;
typescript
类型安全增强
// 使用泛型增强复用性
export abstract class AbstractRepository<T> {
abstract find(): Promise<T[]>;
abstract create(entity: T): Promise<T>;
}
typescript
2.2 具体实现类
2.2.1 Mongoose实现
// user-mongoose.repository.ts
@Injectable()
export class UserMongooseRepository implements UserAbstractRepository {
constructor(
@InjectModel(User.name)
private readonly userModel: Model<UserDocument>
) {}
async find(): Promise<User[]> {
return this.userModel.find().lean().exec(); // 使用lean()提升性能
}
// 扩展实现
async findByEmail(email: string): Promise<User | null> {
return this.userModel.findOne({ email }).lean().exec();
}
}
typescript
性能优化技巧
- 查询优化:添加索引
@Schema() export class User { @Prop({ index: true, unique: true }) email: string; }
typescript - 批量操作:实现批量插入
async bulkCreate(users: User[]): Promise<void> { await this.userModel.insertMany(users); }
typescript
2.2.2 TypeORM实现
// user-typeorm.repository.ts
@Injectable()
export class UserTypeormRepository implements UserAbstractRepository {
constructor(
@InjectRepository(UserEntity)
private readonly repository: Repository<UserEntity>
) {}
async find(): Promise<User[]> {
return this.repository.find({
relations: ['profile'], // 关联查询
cache: true // 启用缓存
});
}
// 事务示例
async transferCredits(fromId: string, toId: string, amount: number) {
await this.repository.manager.transaction(async manager => {
await manager.decrement(UserEntity, { id: fromId }, 'credits', amount);
await manager.increment(UserEntity, { id: toId }, 'credits', amount);
});
}
}
typescript
高级特性
- 查询构建器:
async findAdmins(): Promise<User[]> { return this.repository.createQueryBuilder('user') .where('user.isAdmin = :isAdmin', { isAdmin: true }) .getMany(); }
typescript
2.2.3 Prisma实现
// user-prisma.repository.ts
@Injectable()
export class UserPrismaRepository implements UserAbstractRepository {
constructor(private readonly prisma: PrismaService) {}
async find(): Promise<User[]> {
return this.prisma.user.findMany({
include: { posts: true }, // 关联查询
where: { active: true } // 条件过滤
});
}
// 原生SQL示例
async countActiveUsers(): Promise<number> {
const result = await this.prisma.$queryRaw<[{ count: bigint }]>(
Prisma.sql`SELECT COUNT(*) FROM users WHERE active = true`
);
return Number(result[0].count);
}
}
typescript
Prisma特有优势
- 类型安全查询:
async findUsersWithPosts(): Promise<(User & { posts: Post[] })[]> { return this.prisma.user.findMany({ include: { posts: true } }); }
typescript
实现对比表
特性 | Mongoose | TypeORM | Prisma |
---|---|---|---|
适合场景 | 文档数据库 | 关系型数据库 | 全类型数据库 |
类型安全 | 中等 | 良好 | 优秀 |
迁移工具 | 需要第三方 | 内置 | 内置 |
原生SQL支持 | 有限 | 完全支持 | 完全支持 |
事务支持 | 支持 | 完善 | 完善 |
💡 提示:在实际项目中,可以根据团队技术栈选择最适合的ORM工具,也可以组合使用多种实现
三、多租户适配器
3.1 创建统一接口
// user.interface.ts
export interface UserAdapter {
find(): Promise<User[]>;
create(user: User): Promise<User>;
update(id: string, user: Partial<User>): Promise<User>;
delete(id: string): Promise<void>;
}
typescript
接口设计原则
- 最小接口原则:只暴露必要的操作
- 前后一致:保持CRUD操作命名一致性
- 扩展性:支持未来添加新方法
类型增强建议
// 支持分页和过滤的扩展接口
export interface UserAdapter {
find(options?: {
page?: number;
limit?: number;
where?: Partial<User>
}): Promise<{ data: User[]; total: number }>;
}
typescript
错误处理规范
// 定义统一的错误类型
export interface RepositoryError {
code: string;
message: string;
details?: unknown;
}
export interface UserAdapter {
find(): Promise<User[] | RepositoryError>;
}
typescript
3.2 动态Repository选择
// user.repository.ts
@Injectable()
export class UserRepository {
constructor(
private readonly request: Request,
private readonly mongooseRepo: UserMongooseRepository,
private readonly typeormRepo: UserTypeormRepository,
private readonly prismaRepo: UserPrismaRepository
) {}
private getRepository(): UserAdapter {
const tenantId = this.request.headers['tenant-id'];
switch(tenantId) {
case 'mongoose': return this.mongooseRepo;
case 'typeorm': return this.typeormRepo;
case 'prisma': return this.prismaRepo;
default: throw new Error('Invalid tenant');
}
}
async find(): Promise<User[]> {
return this.getRepository().find();
}
}
typescript
高级多租户实现方案
- 基于数据库的选择策略
private async getRepository(): Promise<UserAdapter> {
const tenantConfig = await this.tenantService.getConfig(this.request);
return this.repositories.get(tenantConfig.databaseType);
}
typescript
- 缓存优化
private repositoryCache = new Map<string, UserAdapter>();
private getRepository(): UserAdapter {
const tenantId = this.request.headers['tenant-id'];
if (!this.repositoryCache.has(tenantId)) {
const repo = this.createRepository(tenantId);
this.repositoryCache.set(tenantId, repo);
}
return this.repositoryCache.get(tenantId)!;
}
typescript
- 策略模式实现
// repository.strategy.ts
export class RepositoryStrategy {
private strategies = new Map<string, UserAdapter>();
register(type: string, adapter: UserAdapter) {
this.strategies.set(type, adapter);
}
get(type: string): UserAdapter {
const adapter = this.strategies.get(type);
if (!adapter) throw new Error(`No strategy for ${type}`);
return adapter;
}
}
typescript
多租户架构模式对比
模式类型 | 实现方式 | 隔离级别 | 适用场景 |
---|---|---|---|
数据库级 | 每个租户独立数据库 | 最高 | 金融、医疗等高安全需求 |
Schema级 | 共享数据库独立schema | 中 | 中等规模SaaS应用 |
数据标记 | 共享表通过tenant_id区分 | 低 | 小型低成本应用 |
安全增强建议
- 租户ID验证
private validateTenant(tenantId: string) {
if (!this.validTenants.includes(tenantId)) {
throw new UnauthorizedException('Invalid tenant');
}
}
typescript
- 数据过滤中间件
@Injectable()
export class TenantFilterMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const tenantId = req.headers['tenant-id'];
// 自动为所有查询添加tenant_id条件
req.query.tenantId = tenantId;
next();
}
}
typescript
性能监控指标
- 租户切换耗时
- 各租户请求量统计
- 数据库操作响应时间(按租户)
💡 专业建议:对于大型多租户系统,考虑使用专门的租户管理服务(如Apache ShardingSphere)来实现更精细的资源隔离和流量控制。
四、控制器集成
4.1 简化Controller
@Controller('users')
export class UserController {
constructor(
private readonly userRepository: UserRepository
) {}
@Get()
async findAll() {
return this.userRepository.find();
}
}
typescript
架构优势解析
- 单一职责原则:控制器仅处理HTTP请求/响应逻辑
- 依赖注入:通过构造函数注入解耦业务逻辑
- 接口统一:所有数据访问通过Repository抽象层
完整CRUD实现示例
@Controller('users')
export class UserController {
constructor(
private readonly userRepository: UserRepository
) {}
@Get()
async findAll(@Query() query: PaginationQuery) {
return this.userRepository.find({
page: query.page,
limit: query.limit
});
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.userRepository.findById(id);
}
@Post()
async create(@Body() createUserDto: CreateUserDto) {
return this.userRepository.create(createUserDto);
}
@Patch(':id')
async update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto
) {
return this.userRepository.update(id, updateUserDto);
}
@Delete(':id')
async remove(@Param('id') id: string) {
return this.userRepository.delete(id);
}
}
typescript
高级功能扩展
- 请求验证:
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createUserDto: CreateUserDto) {
// ...
}
typescript
- 缓存支持:
@Get(':id')
@CacheTTL(60) // 缓存60秒
async findOne(@Param('id') id: string) {
// ...
}
typescript
- OpenAPI文档:
@ApiOperation({ summary: '获取用户列表' })
@Get()
async findAll() {
// ...
}
typescript
性能优化技巧
- 流式响应(大数据量场景):
@Get('export')
async exportUsers(@Res() res: Response) {
const stream = await this.userRepository.getExportStream();
stream.pipe(res);
}
typescript
- 并行请求处理:
@Get('stats')
async getStats() {
const [count, active] = await Promise.all([
this.userRepository.count(),
this.userRepository.countActive()
]);
return { count, active };
}
typescript
安全增强方案
- 权限控制:
@Roles('admin')
@Delete(':id')
async remove(@Param('id') id: string) {
// ...
}
typescript
- 速率限制:
@Throttle(100, 60) // 每分钟100次
@Post('login')
async login(@Body() credentials: LoginDto) {
// ...
}
typescript
单元测试示例
describe('UserController', () => {
let controller: UserController;
let mockRepository: jest.Mocked<UserRepository>;
beforeEach(async () => {
mockRepository = {
find: jest.fn().mockResolvedValue([testUser]),
create: jest.fn()
};
const module = await Test.createTestingModule({
controllers: [UserController],
providers: [
{ provide: UserRepository, useValue: mockRepository }
]
}).compile();
controller = module.get<UserController>(UserController);
});
it('应该返回用户列表', async () => {
await expect(controller.findAll()).resolves.toEqual([testUser]);
expect(mockRepository.find).toBeCalledTimes(1);
});
});
typescript
最佳实践建议
- 保持控制器精简:复杂业务逻辑应放在Service层
- 统一异常处理:使用过滤器处理Repository抛出的异常
- 版本控制:为API添加版本前缀(如
/v1/users
) - 文档生成:集成Swagger自动生成API文档
💡 专业提示:对于大型项目,可以考虑使用CQRS模式进一步分离读写操作:
@QueryHandler(GetUsersQuery)
export class GetUsersHandler implements IQueryHandler<GetUsersQuery> {
constructor(private readonly userRepository: UserRepository) {}
async execute(query: GetUsersQuery) {
return this.userRepository.find(query);
}
}
typescript
五、优化优势
5.1 架构优势对比
特性 | 优化前状态 | 优化后状态 | 改进效果 |
---|---|---|---|
模块耦合度 | 高 ⚠️ | 低 ✅ | 业务逻辑与数据库实现完全解耦,修改数据库类型不会影响业务代码 |
扩展性 | 差 ⚠️ | 好 ✅ | 新增数据库支持只需添加实现类,无需修改现有代码 |
代码复用 | 低 ⚠️ | 高 ✅ | 通用数据库操作封装在抽象层,各实现类复用核心逻辑 |
多数据库支持 | 困难 ⚠️ | 简单 ✅ | 通过统一接口和动态切换机制,一套代码可同时支持MySQL/MongoDB/PostgreSQL等多种数据库 |
可测试性 | 困难 ⚠️ | 容易 ✅ | 抽象接口方便mock测试,各数据库实现可独立测试 |
维护成本 | 高 ⚠️ | 低 ✅ | 数据库相关变更集中在独立模块,影响范围可控 |
深度解析:多数据库支持实现原理
5.2 最佳实践详解
1. 抽象层设计进阶方案
- 多级抽象:
// 基础CRUD抽象 export interface CrudRepository<T> { findAll(): Promise<T[]>; findById(id: string): Promise<T>; } // 扩展抽象 export interface UserRepository extends CrudRepository<User> { findByEmail(email: string): Promise<User>; search(keyword: string): Promise<User[]>; }
typescript
2. 动态数据源增强实现
- 配置中心集成:
private getDataSource() { const config = this.configService.get<DataSourceConfig>(`datasources.${this.tenantId}`); return DataSourceFactory.create(config); }
typescript - 连接池管理:
@Injectable() export class ConnectionPool { private pools = new Map<string, DataSource>(); getConnection(tenantId: string) { if (!this.pools.has(tenantId)) { this.pools.set(tenantId, this.createPool(tenantId)); } return this.pools.get(tenantId)!; } }
typescript
3. 模块化组织规范
- 目录结构示例:
src/ ├── database/ │ ├── abstract/ │ ├── mysql/ │ ├── mongodb/ │ ├── shared/ │ └── database.module.ts
text - 模块依赖关系:
4. 接口约束技术
- 运行时校验:
function implementsInterface(obj: any, interfaceDef: InterfaceDefinition) { return interfaceDef.methods.every(method => typeof obj[method] === 'function'); }
typescript - 类型守卫:
function isUserRepository(obj: any): obj is UserRepository { return 'findByEmail' in obj && typeof obj.findByEmail === 'function'; }
typescript
性能优化对比
典型应用场景
- SaaS多租户系统:不同租户使用不同数据库引擎
- 数据库迁移过程:新旧数据库并行运行
- 多云部署架构:不同区域部署不同数据库服务
扩展学习资源
💡 专家建议:对于超大规模系统,可将数据库选择策略下沉到API网关层,通过请求头X-DB-Engine
直接路由到对应数据库集群,进一步提升性能。
↑